from datetime import datetime
from copy import deepcopy
import os
import json
import logging
import pandas as pd

from django.contrib.auth import logout
from django.http import FileResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.files.base import ContentFile
from django.utils.timezone import make_aware
from django.utils.datastructures import MultiValueDictKeyError

from routing_django.settings import MEDIA_ROOT, OPT_BACK_PATH, BASE_DIR
from ui.models import FileModel, OrderModel, RoutesModel, SheetsModel, RouteFigsModel
from ui.forms import FilesForm, OrdersForm, RoutesForm, SheetsForm, RouteFigsForm

from opt_back.gvkm_data_converter import GvkmDataConverter
from opt_back.optimization import Optimizer
from opt_back.visualization import Visualizer
from opt_back.opt_utils import np_converter, safe_cast

converter = GvkmDataConverter(config_path="config/gvkm_data_converter_params.json", opt_back_path=OPT_BACK_PATH)
optimizer = Optimizer(config_path="config/optimization_params.json", opt_back_path=OPT_BACK_PATH)
visualizer = Visualizer(config_path='config/visualization_params.json', opt_back_path=OPT_BACK_PATH)

logging.basicConfig(
    filename=os.path.join(BASE_DIR,'logs/django.log'),
    filemode='a',
    level=logging.DEBUG,
    format='%(asctime)s,%(msecs)d\t%(name)s\t%(levelname)s\t%(message)s',
    datefmt='%Y-%m-%d-%H:%M:%S')
logger = logging.getLogger('django')


@login_required
def index(request):
    logger.info('Got \'index\' request')
    return render(request, 'index.html')


@login_required
def files(request):
    logger.info('Got \'files\' request')
    last_files = FileModel.objects.order_by('-datetime')[:10]
    context = {
        'last_files': last_files
    }

    if request.method == 'POST':
        files_form = FilesForm(request.POST, request.FILES)
        if not files_form.is_valid():
            messages.error(request, 'Form is not valid!')
            return render(request, 'files.html')
        try:
            model = FileModel(datetime=make_aware(datetime.now()), user=request.user.username,
                              file_field=request.FILES['order_file'])
            # model.save(rows_num=request.FILES['order_file'])
            logger.info(f'User uploaded file {model.file_field.name}')
        except MultiValueDictKeyError as e:
            messages.error(request, e)
        except Exception as e:
            logger.warning(f'Exception while uploading file: {e}')
            messages.error(request, e)
    else:
        files_form = FilesForm()
    context['files_form'] = files_form
    return render(request, 'files.html', context)


@login_required
def orders(request):
    logger.info('Got \'orders\' request')
    last_orders = OrderModel.objects.order_by('-datetime')[:10]
    context = {'last_orders': last_orders}

    if request.method == 'POST':
        orders_form = OrdersForm(request.POST)
        if not orders_form.is_valid():
            messages.error(request, 'Form is not valid!')
            return render(request, 'orders.html')
        try:
            excel_path = os.path.join(MEDIA_ROOT, orders_form.cleaned_data['order_files'].file_field.name)
            logger.debug(f'Converting Excel to orders DataFrame: {excel_path}')
            chosen_date = orders_form.cleaned_data['order_date']
            city = orders_form.cleaned_data['city']
            # is_soft = orders_form.cleaned_data['soft']
            # vkm_count = safe_cast(orders_form.cleaned_data['vkm_count'], int)
            # capacity = safe_cast(orders_form.cleaned_data['capacity'], int)
            lat = safe_cast(orders_form.cleaned_data['lat'], float)
            lon = safe_cast(orders_form.cleaned_data['lon'], float)

            day_orders, error, warnings = converter.excel_to_orders(excel_path, chosen_date, city=city)

            # Show alerts on warnings or error
            for warning in warnings:
                logger.warning(f'Got warning while processing orders: {warning}')
                messages.warning(request, warning)

            if error:
                logger.error(f'Got error while processing orders: {error}')
                messages.error(request, error)
                return render(request, 'orders.html', context)

            # Time window plot
            # tw_fig_path = os.path.join(MEDIA_ROOT, 'window_figures',
                                       # datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) + '.html'
            # logger.debug(f'Saving time window figure to {tw_fig_path}')
            # time_windows_fig = visualizer.draw_time_windows(day_orders, vkm_count, tw_fig_path)
            # context['time_windows_plot'] = time_windows_fig

            logger.debug(f'Converting orders DataFrame into json')
            req_json, day_orders = converter.orders_to_json(
                day_orders, 1,
                city=city, capacity=None, is_soft=None,
                lon=lat, lat=lon
            )

            # Scatter plot
            scatter_fig_path = os.path.join(MEDIA_ROOT, 'scatterplots',
                                            datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) + '.html'
            logger.debug(f'Saving scatter plot to {scatter_fig_path}')

            scatter_fig = visualizer.draw_scatter_points(day_orders, city, scatter_fig_path)
            context['scatter_plot'] = scatter_fig

            logger.debug(f'Updating OrderModel')
            # filename = str(chosen_date) + '_' + str(vkm_count) + '.json'
            filename = str(chosen_date) + '.json'
            model = OrderModel(datetime=make_aware(datetime.now()), user=request.user.username,
                               file_field=ContentFile(content=json.dumps(req_json), name=filename))
            model.save()
            return render(request, 'time_windows_figure.html', context)
        except Exception as e:
            messages.error(request, e)
    else:
        orders_form = OrdersForm()
    context['orders_form'] = orders_form
    return render(request, 'orders.html', context)


@login_required
def routing(request):
    logger.info('Got \'routing\' request')
    last_routes = RoutesModel.objects.order_by('-datetime')[:10]
    context = {'last_routes': last_routes}
    now = datetime.now()
    if request.method == 'POST':
        routes_form = RoutesForm(request.POST)
        if not routes_form.is_valid():
            messages.error(request, 'Form is not valid!')
            return render(request, 'routing.html')
        try:
            logger.debug(f"Reading json with orders: {routes_form.cleaned_data['route_files'].file_field.name}")
            req_json = routes_form.cleaned_data['route_files'].get_json()

            best_route = None
            is_soft = routes_form.cleaned_data['soft']
            vkm_count = safe_cast(routes_form.cleaned_data['vkm_count'], int)
            capacity = safe_cast(routes_form.cleaned_data['capacity'], int)

            city, n_orders = req_json['city'], len(req_json['orders'])
            min_vkm, max_vkm = vkm_count, vkm_count + 1
            if is_soft:
                max_vkm = n_orders

            tmp_delivers = [deepcopy(req_json['delivers'][0]) for _ in range(max_vkm)]
            for i in range(len(tmp_delivers)):
                tmp_delivers[i]['id'] = i
                tmp_delivers[i]['descr'] = str(i + 1)

            req_json['delivers'] = tmp_delivers
            req_json['capacity'], req_json['is_soft'] = capacity, is_soft
            # capacity = req_json['capacity']
            # is_soft = req_json['is_soft']

            logger.debug(f'Preparing data')
            data, distances, warnings = optimizer.data_converter.prepare_data(req_json)

            for warning in warnings:
                logger.warning(f'Got warning while building DM: {warning}')
                messages.warning(request, warning)

            # Arthur
            increment = 0
            for _ in range(min_vkm, max_vkm):
                tmp_json = {**req_json}
                tmp_json['delivers'] = tmp_json['delivers'][:min_vkm + increment]
                logger.debug(f'Preparing data')
                data, _, _ = optimizer.data_converter.prepare_data(tmp_json, distances=distances)
                logger.debug(f'Finding optimal routes')
                routes = optimizer.get_routes(tmp_json, data, capacity)
                logger.debug(f'Picking the best route')
                best_route = optimizer.pick_best_route(routes)
                increment += 1

                if best_route:
                    break
            
            if not best_route:
                raise Exception('Не удалось построить маршрут')

            node_diff = set(data['request_id'].dropna()).difference(
                set([point['request_id'] for subroute in best_route['subroutes'] for point in subroute['points']]))
            if len(node_diff) != 0:
                messages.error(request, f'Не удалось включить в маршрут заказы: {str(node_diff)}')

            logger.debug(f'Updating routes model')
            filename = now.strftime('%Y-%m-%d-%H-%M-%S') + '.json'
            routes_json = json.dumps(routes, default=np_converter)
            routes_model = RoutesModel(datetime=make_aware(now), user=request.user.username,
                                       file_field=ContentFile(content=routes_json, name=filename))
            routes_model.save()

            filename = filename.split('.')[0] + '.xlsx'
            logger.debug(f'Forming Excel with VKM routes: {filename}')
            excel_content = converter.route_to_excel(best_route)
            logger.debug(f'Updating SheetsModel: {filename}')
            sheets_model = SheetsModel(datetime=make_aware(now), user=request.user.username,
                                       file_field=ContentFile(excel_content, name=filename))
            sheets_model.save()

            logger.debug(f"Visualizing the best route: {now.strftime('%Y-%m-%d-%H-%M-%S') + '.html'}")
            routes_fig = visualizer.draw_plotly_routes(best_route, city)
            context['routes_fig'] = routes_fig

            logger.debug(f'Updating route figure model')
            route_figs_model = RouteFigsModel(datetime=make_aware(now), user=request.user.username, html=routes_fig)
            route_figs_model.save()

            return render(request, 'routes_figure.html', context)
        except Exception as e:
            messages.error(request, e)

    routes_form = RoutesForm()
    context['routes_form'] = routes_form
    return render(request, 'routing.html', context)


@login_required
def routes(request):
    logger.info('Got \'routes\' request')
    last_figs = RouteFigsModel.objects.order_by('-datetime')[:10]
    context = {'last_figs': last_figs}

    if request.method == 'POST':
        route_figs_form = RouteFigsForm(request.POST)
        if not route_figs_form.is_valid():
            messages.error(request, 'Form is not valid!')
            return render(request, 'routes.html')
        logger.debug(f'Opening routes figure')
        context['routes_fig'] = route_figs_form.cleaned_data['figures'].html
        return render(request, 'routes_figure.html', context)
    else:
        route_figs_form = RouteFigsForm()
    context['route_figs_form'] = route_figs_form
    return render(request, 'routes.html', context)


@login_required
def route_sheets(request):
    logger.info('Got \'route_sheets\' request')
    if request.method == 'POST':
        sheets_form = SheetsForm(request.POST)
        if not sheets_form.is_valid():
            messages.error(request, 'Form is not valid!')
            return render(request, 'route_sheets.html')
        try:
            logger.info(f"User downloaded file {sheets_form.cleaned_data['sheet_files']}")
            excel_contents = sheets_form.cleaned_data['sheet_files'].get_excel()
            return FileResponse(excel_contents, as_attachment=True)
        except Exception as e:
            logger.warning(f'User couldn\'t download file')
            messages.error(request, e)
    else:
        sheets_form = SheetsForm()
    context = {'sheets_form': sheets_form}
    return render(request, 'route_sheets.html', context)


def logout_user(request):
    logger.info('Got \'logout\' request')
    logout(request)
    return HttpResponseRedirect("/")
